package com.ehi.common.jersey.util;

import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.glassfish.jersey.apache.connector.ApacheClientProperties;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;

@SuppressWarnings("deprecation")
@Component
public class RestClientService {

    public static final String HTTP = "http";
    public static final int MAX_CONNECTION = 200;
    public static final int DEFAULT_MAX_PER_ROUTE = 200;
    private static final Logger LOG = LoggerFactory.getLogger(RestClientService.class);
    private static final String HTTPS = "https";
    private static Client httpClient;
    private static Client httpsClient;

    public WebTarget getWebTarget(String url) {
        URI uri = URI.create(url);
        if (HTTP.equals(uri.getScheme())) {
            if (httpClient == null) {
                initHttpClient();
            }
            return httpClient.target(url);
        } else if (HTTPS.equals(uri.getScheme())) {
            if (httpsClient == null) {
                initHttpsClient();
            }
            return httpsClient.target(url);
        }
        return null;
    }

    private synchronized void initHttpClient() {
        if (httpClient != null)
            return;

        ClientConfig clientConfig = new ClientConfig();
        final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();

        connectionManager.setMaxTotal(MAX_CONNECTION);
        connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);

        clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager);
        clientConfig.connectorProvider(new ApacheConnectorProvider());

        httpClient = ClientBuilder.newClient(clientConfig);
        httpClient.register(JacksonFeature.class);
    }

    private synchronized void initHttpsClient() {
        if (httpsClient != null)
            return;

        try {
            ClientConfig clientConfig = createClientConfig();
            httpsClient = ClientBuilder.newBuilder().withConfig(clientConfig).build();
        } catch (Exception e) {
            LOG.error("Load trust store failed!", e);
            throw new IllegalStateException(e);
        }
    }

    private ClientConfig createClientConfig() throws Exception {
        ClientConfig clientConfig = new ClientConfig();

        HostnameVerifier hostnameVerifier = new CustomX509HostnameVerifier();
        SSLContext sslContext = createSslContext();

        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);

        final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslSocketFactory).build();

        final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);

        connectionManager.setMaxTotal(MAX_CONNECTION);
        connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);

        clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager);
        clientConfig.connectorProvider(new ApacheConnectorProvider());

        return clientConfig;
    }

    private SSLContext createSslContext() throws NoSuchAlgorithmException, KeyManagementException {
        TrustManager[] trustAllCerts = new TrustManager[]{new CustomX509TrustManager()};
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new SecureRandom());
        return sslContext;
    }

    private static class CustomX509TrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(
            java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(
            java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException {

        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }

    private static class CustomX509HostnameVerifier implements HostnameVerifier {

        @Override
        public boolean verify(String arg0, SSLSession arg1) {
            return true;
        }
    }

}
